﻿using FDStudio.Core.Config;
using FDStudio.Core.Entities;
using FDStudio.Core.Entities.Enums;
using FDStudio.Core.Entities.Query;
using FDStudio.Core.Exceptions;
using FDStudio.Core.Extensions;
using FDStudio.Core.Generates;
using FDStudio.Core.Runtime;
using FDStudio.Core.Utils;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace FDStudio.Core.Repositories
{
    /*
     CreatedOn: 2018-01-26
     CreatedBy: Jackie Lee(天宇遊龍)
     */
    /// <summary>
    /// 基本仓储上下文
    /// </summary>
    public abstract class DefaultRepositoryContext : BaseContext, IDefaultRepositoryContext
    {
        /// <summary>
        /// 模型生成器
        /// </summary>
        protected IModelGenerator modelGenerator => RuntimeContext.Current.Container.Resove<IModelGenerator>();

        /// <summary>
        /// 数据库类型
        /// </summary>
        public abstract DatabaseType DatabaseType { get; }

        /// <summary>
        /// 连接字符串
        /// </summary>
        public string ConnectionString { get; private set; }

        /// <summary>
        /// 数据连接配置
        /// </summary>
        public IDbConnectionString DbConnectionString { get; private set; }

        /// <summary>
        /// 当插入数据时，获取新插入值自增列
        /// </summary>
        public abstract string AddedIDString { get; }

        /// <summary>
        /// 分页处理
        /// 如需调用PageList返回结果，此项需要处理
        /// 参数依次为：原查询语句，查询页索引（1开始），查询页大小，返回分页的语句
        /// </summary>
        public abstract Func<string, long, long, SortBy, string> PagingAction { get; }
        
        public DefaultRepositoryContext()
        {
            Switch(DatabaseType);
        }

        /// <summary>
        /// 切换
        /// </summary>
        /// <param name="databaseType"></param>
        /// <param name="name"></param>
        public void Switch(DatabaseType databaseType, string name = "")
        {
            var connConfigs = RuntimeContext.Current.Container.ResoveAll<IDbConnectionString>();
            if (connConfigs == null || connConfigs.Count() == 0)
                throw new DbConnectionConfigException("当前上下文中不存在数据库连接类型");
            var config = connConfigs.FirstOrDefault(c => c.DatabaseType == DatabaseType && (name.IsNullOrEmpty() ? true : c.Name == name));
            if (config == null)
                throw new DbConnectionConfigException($"当前上下文中不存在{DatabaseType}类型数据库连接类型");
            ConnectionString = config.ConnectionString;
            DbConnectionString = config;
        }

        private IDbCommand CreateCommand(IDbConnection connection)
        {
            return connection.CreateCommand();
        }

        public virtual bool Add<T>(T model) where T : class, IEntity
        {
            var sql = modelGenerator.InsertString(model, out IDictionary<string, object> parameters);
            Func<IDbConnection, bool> add = conn =>
             {
                 bool res = ExecuteNonQuery(_dbConnection, sql, parameters, CommandType.Text) > 0;
                 if (res && !AddedIDString.IsNullOrEmpty())
                 {
                     var obj = GetCommand(_dbConnection, AddedIDString, CommandType.Text).ExecuteScalar();
                     model.Id = obj.ToLong();
                 }
                 return res;
             };
            if (IsTranscation)
            {
                return add(_dbConnection);
            }
            using (var connection = CreateConnection())
            {
                return add(connection);
            }
        }

        private int ExecuteNonQuery(IDbConnection connection, string sql, object parameter, CommandType commandType)
        {
            return GetCommand(connection, sql, parameter, commandType).ExecuteNonQuery();
        }

        private IDbCommand GetCommand(IDbConnection connection, string sql, CommandType commandType)
        {
            if (connection.State != ConnectionState.Open)
            {
                connection.Open();
            }
            var cmd = CreateCommand(connection);
            cmd.CommandText = sql;
            cmd.CommandType = commandType;
            return cmd;
        }

        private IDbCommand GetCommand(IDbConnection connection, string sql, object param, CommandType commandType)
        {
            var cmd = GetCommand(connection, sql, commandType);
            SqlCommHelper.AddParameter(cmd, param);
            return cmd;
        }

        public virtual bool Update<T>(T model) where T : class, IEntity
        {
            var sql = modelGenerator.UpdateString<T>(t => t.Id == t.Id);
            if (IsTranscation)
            {
                return ExecuteNonQuery(_dbConnection, sql, model, CommandType.Text) > 0;
            }

            using (var connection = CreateConnection())
            {
                return ExecuteNonQuery(connection, sql, model, CommandType.Text) > 0;
            }
        }

        public virtual bool Delete<T>(T model) where T : class, IEntity
        {
            return Delete<T>(t => t.Id == model.Id);
        }

        public virtual bool Delete<T>(Expression<Func<T, bool>> condition) where T : class, IEntity
        {
            var sql = modelGenerator.DeleteString(condition);
            if (IsTranscation)
            {
                return ExecuteNonQuery(_dbConnection, sql, null, CommandType.Text) > 0;
            }
            using (var connection = CreateConnection())
            {
                return ExecuteNonQuery(connection, sql, null, CommandType.Text) > 0;
            }
        }

        public virtual void Dispose()
        {
            DisposeTransaction(true);
        }

        public virtual void ExecuteDataReader(string sql, Action<IDataReader> read, object parameter, CommandType commandType = CommandType.Text)
        {
            using (var connection = CreateConnection())
            {
                var cmd = GetCommand(connection, sql, parameter, commandType);
                //SqlCommHelper.AddParameter(cmd, parameter);

                using (var reader = cmd.ExecuteReader(CommandBehavior.CloseConnection))
                {
                    read?.Invoke(reader);
                }
            }
        }

        public virtual int ExecuteNonQuery(string sql, object parameter, CommandType commandType = CommandType.Text)
        {
            Func<IDbConnection, int> exec = conn =>
             {
                 var cmd = GetCommand(conn, sql, parameter, commandType);
                 return cmd.ExecuteNonQuery();
             };
            if (IsTranscation)
            {
                return exec(_dbConnection);
            }
            using (var connection = CreateConnection())
            {
                return exec(connection);
            }
        }

        public virtual T ExecuteScalar<T>(string sql, object parameter, CommandType commandType = CommandType.Text)
        {
            using (var connection = CreateConnection())
            {
                var cmd = GetCommand(connection, sql, commandType);
                var obj = cmd.ExecuteScalar();
                if (obj == null)
                {
                    return default(T);
                }
                return (T)Convert.ChangeType(obj, typeof(T));
            }
        }

        public virtual T Get<T>(long id) where T : class, IEntity
        {
            return Get<T>(t => t.Id == id);
        }

        public virtual T Get<T>(Expression<Func<T, bool>> conidtion) where T : class, IEntity
        {
            return GetList(conidtion, null).FirstOrDefault();
        }

        public int Count<T>(Expression<Func<T, bool>> condition) where T : class, IEntity
        {
            var sql = modelGenerator.CountString(condition);
            return ExecuteScalar<int>(sql, null);
        }

        private IList<T> Read<T>(IDataReader reader) where T : class, IEntity
        {
            var props = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public);
            var result = new List<T>();
            while (reader.Read())
            {
                var item = Activator.CreateInstance<T>();
                for (int i = 0; i < reader.FieldCount; ++i)
                {
                    var name = reader.GetName(i);
                    var p = props.Where(pi => pi.Name == name).FirstOrDefault();
                    if (p == null)
                        continue;
                    var value = reader[name];

                    try
                    {
                        p.SetValue(item, value == DBNull.Value ? null : value);
                    }
                    catch
                    {
                        if (value.GetType() == typeof(SByte))
                        {
                            p.SetValue(item, Convert.ChangeType(value, typeof(byte)));
                        }
                        else if (p.PropertyType == typeof(bool))
                        {
                            p.SetValue(item, (byte)Convert.ChangeType(value, typeof(byte)) == 1);
                        }
                        else
                        {
                            throw;
                        }
                    }

                }
                result.Add(item);
            }
            return result;
        }

        public virtual IEnumerable<T> GetList<T>(Expression<Func<T, bool>> condition, IQueryOrder<T> orderBy) where T : class, IEntity
        {
            using (var connection = CreateConnection())
            {
                var sql = modelGenerator.QueryString(condition, orderBy);
                var result = default(IEnumerable<T>);
                ExecuteDataReader(sql, reader =>
                {
                    result = Read<T>(reader);
                }, null);
                return result;
            }
        }

        /// <summary>
        /// 分页
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="condition"></param>
        /// <param name="pagingAction"></param>
        /// <param name="pageIndex"></param>
        /// <param name="pageSize"></param>
        /// <param name="orderBy"></param>
        /// <returns></returns>
        public PagedList<T> GetList<T>(Expression<Func<T, bool>> condition, long pageIndex, long pageSize, IQueryOrder<T> orderBy) where T : class, IEntity
        {
            var sql = modelGenerator.QueryString(condition, orderBy);
            sql = PagingAction?.Invoke(sql, pageIndex, pageSize, orderBy?.KeySortBy ?? SortBy.ASC);
            using (var connection = CreateConnection())
            {
                var result = default(IEnumerable<T>);
                ExecuteDataReader(sql, reader =>
                {
                    result = Read<T>(reader);
                }, null);
                sql = modelGenerator.CountString(condition);
                var count = ExecuteScalar<long>(sql, null);
                return PagedList<T>.Create(result, count, pageSize, pageIndex);
            }
        }
    }
}
